package com.zengdada.authority.service;

import com.alibaba.fastjson.JSON;
import com.github.pagehelper.PageHelper;
import com.github.pagehelper.PageInfo;
import com.zengdada.authority.annotation.ModelColumnEnhance;
import com.zengdada.authority.annotation.SysLog;
import com.zengdada.authority.annotation.SysLogType;
import com.zengdada.authority.exception.RoutineException;
import com.zengdada.authority.service.impl.PublicSqlService;
import com.zengdada.authority.utils.CommonUtils;
import com.zengdada.authority.utils.mapper.Page;
import com.zengdada.authority.utils.mapper.Sort;
import com.zengdada.authority.utils.where.ExampleWhere;
import com.zengdada.authority.utils.where.Where;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.apache.commons.text.StringEscapeUtils;
import org.springframework.beans.factory.BeanFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import tk.mybatis.mapper.common.Mapper;
import tk.mybatis.mapper.entity.Example;

import javax.annotation.PostConstruct;
import javax.persistence.Column;
import javax.persistence.Id;
import javax.persistence.Table;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import java.util.*;

/**
 * @Auther: 12614
 * @Contact: 1261438511@qq.com
 * @Date: 2018/11/21 10:19
 * @Description: service 增强类 （创建了对应的mapper接口（（使用时请显示强转），初始化了BaseService中接口方法）
 */
@Slf4j
public abstract class EnhanceBaseService<T> implements BaseService<T> {

    private static final String APPLICATION_ID = "APPLICATION_ID";

    @Value("${authority.application.id}")
    private String applicationId;
    @Autowired
    protected PublicSqlService sqlService;
    @Autowired
    BeanFactory beanFactory;
    String tableName = null;
    /**
     * 数据库字段名
     */
    protected final List<String> columns = new ArrayList<>();
    /**
     * 数据库字段名
     */
    protected final Map<String, Class<?>> idColumns = new HashMap<>();
    /**
     * 实体类属性名
     */
    protected final Map<String, String> fieldNames = new HashMap<>();
    /**
     * 增强属性
     */
    Map<String, Map<String, String>> enhanceColumns = new HashMap<>();
    protected Mapper<T> mapper = null;
    Class<T> actualClass;

    @PostConstruct
    public void initEnhanceBaseServiceMethod() {
        try {
            // 获取泛型类型
            Type[] arguments = ((ParameterizedType) this.getClass().getGenericSuperclass()).getActualTypeArguments();
            if (arguments == null || arguments.length == 0) {
                log.error("{}没有设置泛型", this.getClass());
                return;
            }
            actualClass = (Class<T>) arguments[0];
            String firstArgumentName = actualClass.getTypeName();
            String typeName = firstArgumentName.substring(firstArgumentName.lastIndexOf(".") + 1, firstArgumentName.length());
            // 构造mapper bean名（id）
            try {
                String mapperName = String.format("%sMapper", (new StringBuilder()).append(Character.toLowerCase(typeName.charAt(0))).append(typeName.substring(1)).toString());
                mapper = (Mapper<T>) beanFactory.getBean(mapperName);
            } catch (Exception e) {
                //
            }
            if (mapper == null) {
                String mapperName = String.format("%sMapper", typeName);
                mapper = (Mapper<T>) beanFactory.getBean(mapperName);
            }
            // 获取字段信息
            getColumnFields(actualClass, this.columns, this.idColumns, this.fieldNames, this.enhanceColumns);
            if (actualClass != null) {
                if (actualClass.isAnnotationPresent(Table.class)) {
                    Table annotation = actualClass.getAnnotation(Table.class);
                    this.tableName = annotation.name();
                }
                if (StringUtils.isBlank(this.tableName)) {
                    this.tableName = CommonUtils.humpToLine(actualClass.getSimpleName());
                }
            }
        } catch (Exception e) {
            log.error("{} 获取mapper失败：{}", this.getClass(), e.getMessage());
        }
    }


    @Override
    @SysLog(value = "新增数据,来源：EnhanceBaseService", type = SysLogType.SERVICE)
    public int add(T obj) {
        // 判断是否添加 APPLICATION_ID 查询
        if (columns.contains(APPLICATION_ID)) {
            try {
                Field[] fields = actualClass.getDeclaredFields();
                for (Field field : fields) {
                    if (String.class == field.getType() && fieldNames.get(APPLICATION_ID).equals(field.getName())) {
                        // 属性参数值首字母转成大写
                        char[] cs = (field.getName()).toCharArray();
                        cs[0] -= 32;
                        Method setMethod = actualClass.getMethod("set" + String.valueOf(cs), String.class);
                        setMethod.invoke(obj, applicationId);
                    }
                }
            } catch (Exception e) {
                throw new RoutineException("APPLICATION_ID 参数设置值时出错！！");
            }
        }
        return mapper == null ? 0 : mapper.insertSelective(obj);
    }

    @Override
    @SysLog(value = "根据ID修改数据,来源：EnhanceBaseService", type = SysLogType.SERVICE)
    public int updateById(T obj) {
        // 如果存在 APPLICATION_ID 属性 就联合属性修改 防止跨应用修改数据
        if (columns.contains(APPLICATION_ID)) {
            if (obj == null) {
                throw new RoutineException("参数 obj 不能为空！！！");
            }
            Example example = new Example(actualClass);
            Example.Criteria criteria = example.createCriteria();
            try {
                Field[] fields = actualClass.getDeclaredFields();
                for (Field field : fields) {
                    // 检验或者设置 id 属性的条件
                    if (field.isAnnotationPresent(Id.class)) {
                        char[] cs = (field.getName()).toCharArray();
                        cs[0] -= 32;
                        Method getMethod = actualClass.getMethod("get" + String.valueOf(cs));
                        Object value = getMethod.invoke(obj);
                        if (field.isAnnotationPresent(Id.class) && value == null) {
                            throw new RoutineException("参数的所有ID属性都不能为空！！！");
                        }
                        criteria.andEqualTo(field.getName(), value);
                    }
                    if (fieldNames.get(APPLICATION_ID).equals(field.getName()) && String.class == field.getType()) {
                        criteria.andEqualTo(field.getName(), applicationId);
                    }
                }
            } catch (Exception e) {
                throw new RoutineException("参数设置值时出错！！" + e.getMessage());
            }
            return mapper == null ? 0 : mapper.updateByExampleSelective(obj, example);
        } else {
            return mapper == null ? 0 : mapper.updateByPrimaryKeySelective(obj);
        }
    }

    @Override
    @SysLog(value = "根据ID删除数据,来源：EnhanceBaseService", type = SysLogType.SERVICE)
    public int deleteById(Object id) {
        // 如果存在 APPLICATION_ID 属性 就联合属性删除 防止跨应用删除数据
        if (columns.contains(APPLICATION_ID)) {
            if (id == null) {
                throw new RoutineException("参数 id 不能为空！！！");
            }
            T obj;
            try {
                obj = (id.getClass() == actualClass) ? (T) id : actualClass.newInstance();
                Field[] fields = actualClass.getDeclaredFields();
                for (Field field : fields) {
                    // 属性参数值首字母转成大写
                    char[] cs = (field.getName()).toCharArray();
                    cs[0] -= 32;
                    // 检验或者设置 id 属性
                    if (field.isAnnotationPresent(Id.class)) {
                        Method setMethod = actualClass.getMethod("set" + String.valueOf(cs), id.getClass());
                        if (id.getClass() != actualClass) {
                            setMethod.invoke(obj, id);
                        } else {
                            Method getMethod = actualClass.getMethod("get" + String.valueOf(cs));
                            Object invokeId = getMethod.invoke(id);
                            if (invokeId == null) {
                                throw new RoutineException("参数的所有ID属性都不能为空！！！");
                            }
                            setMethod.invoke(obj, invokeId);
                        }
                    }
                    if (fieldNames.get(APPLICATION_ID).equals(field.getName()) && String.class == field.getType()) {
                        Method setMethod = actualClass.getMethod("set" + String.valueOf(cs), String.class);
                        setMethod.invoke(obj, applicationId);
                    }
                }
            } catch (Exception e) {
                throw new RoutineException("参数设置值时出错！！" + e.getMessage());
            }
            return mapper == null ? 0 : mapper.delete(obj);
        } else {
            return mapper == null ? 0 : mapper.deleteByPrimaryKey(id);
        }
    }

    @Override
    public T selectById(Object id) {
        return mapper == null ? null : mapper.selectByPrimaryKey(id);
    }

    @Override
    public Map<String, Object> selectDetailedById(Object id) {
        if (idColumns.size() == 0) {
            throw new RoutineException("当前实体类没有被注释为ID字段");
        }
        List<Where> params = new ArrayList<>();
        Where where = new Where(Where.Formula.AND, params);
        for (String field : idColumns.keySet()) {
            Where.FieldType fieldType = null;
            try {
                fieldType = CommonUtils.enumValueOf(Where.FieldType.class, new HashMap<String, Object>() {{
                    put("javaType", idColumns.get(field));
                }});
            } catch (Exception e) {
                fieldType = Where.FieldType.STRING;
            }
            params.add(new Where(field, String.valueOf(id), fieldType));
        }
        PageInfo<Map<String, Object>> pageInfo = selectDetailedPageInfoByParam(where, null, null);
        if (pageInfo.getList().size() == 0) {
            return null;
        }
        return pageInfo.getList().get(0);
    }

    @Override
    public List<T> selectAll() {
        return selectPageInfoByParam((Where) null, null, null).getList();
    }

    /**
     * 分页查询 （可排序 ）
     *
     * @param page 分页情况 null 时查询所有
     * @param sort 排序情况 null 时排序
     * @return
     */
    @Override
    public PageInfo<T> selectPageInfo(Page page, Sort sort) {
        return selectPageInfoByParam((Where) null, page, sort);
    }

    /**
     * 更具条件（满足PublicSearch类条件的json数组字符串）分页查询 （可排序 ）
     *
     * @param searchs 查询参数（满足条件的json数组字符串） null 时不带条件查询（字段必须为当前实体中存在的，否则会抛出异常）
     * @param page    分页情况 null 时查询所有
     * @param sort    排序情况 null 时排序
     * @return
     */
    @Override
    public PageInfo<T> selectPageInfoByParam(String searchs, Page page, Sort sort) {
        Where where = null;
        try {
            if (!StringUtils.isEmpty(searchs)) {
                where = Where.formJsonString((StringEscapeUtils.unescapeHtml4(searchs)));
            }
        } catch (Exception e) {
            throw new RoutineException("查询条件解析失败：" + e.getMessage());
        }
        return selectPageInfoByParam(where, page, sort);
    }

    /**
     * 更具条件分页查询 （可排序 ） TODO ExampleWhere 可能是有问题的
     *
     * @param param 查询参数（字段必须为当前实体中存在的，否则会抛出异常）
     * @param page  分页情况 null 时查询所有
     * @param sort  排序情况 null 时排序
     * @return
     */
    @Override
    public PageInfo<T> selectPageInfoByParam(Where param, Page page, Sort sort) {
        param = applicationCheck(param);
        Example example = new Example(actualClass);
//        buildCriteria(example.createCriteria(), param, null);
//        if (param != null) {
        JSON.parseObject(JSON.toJSONString(param), ExampleWhere.class).init(example, this.columns, this.fieldNames).analysis();
//        }
        if (sort != null && sort.isNotNull()) {
            example.setOrderByClause(sort.getOrderDetailed(actualClass));
        }
        if (page != null) {
            PageHelper.startPage(page.getPage(), page.getPageSize());
        }
        List<T> objects = mapper == null ? new ArrayList<>() : mapper.selectByExample(example);
        return new PageInfo<>(objects);
    }

    /**
     * 分页查询 （可排序 ）
     *
     * @param page 分页情况 null 时查询所有
     * @param sort 排序情况 null 时排序
     * @return
     */
    @Override
    public PageInfo<Map<String, Object>> selectDetailedPageInfo(Page page, Sort sort) {
        return selectDetailedPageInfoByParam((Where) null, page, sort);
    }

    /**
     * 更具条件（满足PublicSearch类条件的json数组字符串）分页查询 （可排序 ）
     *
     * @param searchs 查询参数（满足条件的json数组字符串） null 时不带条件查询（字段必须为当前实体中存在的，否则会抛出异常）
     * @param page    分页情况 null 时查询所有
     * @param sort    排序情况 null 时排序
     * @return
     */
    @Override
    public PageInfo<Map<String, Object>> selectDetailedPageInfoByParam(String searchs, Page page, Sort sort) {
        Where where = null;
        try {
            if (!StringUtils.isEmpty(searchs)) {
                where = Where.formJsonString((StringEscapeUtils.unescapeHtml4(searchs)));
            }
        } catch (Exception e) {
            throw new RoutineException("查询条件解析失败：" + e.getMessage());
        }
        return selectDetailedPageInfoByParam(where, page, sort);
    }

    /**
     * 更具条件分页查询 （可排序 ）
     *
     * @param where 查询参数（字段必须为当前实体中存在的，否则会抛出异常）
     * @param page  分页情况 null 时查询所有
     * @param sort  排序情况 null 时排序
     * @return
     */
    @Override
    public PageInfo<Map<String, Object>> selectDetailedPageInfoByParam(Where where, Page page, Sort sort) {
        where = applicationCheck(where);
        // 如果没有增强属性则利用原来的方式查询
        if (enhanceColumns == null || enhanceColumns.size() == 0) {
            PageInfo<T> pageInfo = selectPageInfoByParam(where, page, sort);
            PageInfo<Map<String, Object>> result = new PageInfo<>(CommonUtils.batchOrSingleBeanToMap(pageInfo.getList(), actualClass.getPackage().getName()));
            result.setTotal(pageInfo.getTotal());
            return result;
        }
        StringBuffer sql = new StringBuffer();
        sql.append("SELECT ");
        columns.forEach(column -> {
            sql.append("EHC.").append(column).append(", ");
        });
        enhanceColumns.forEach((k, v) -> {
            sql.append(String.format("(%s) %s", v.get("sql").replaceAll("\\?", "EHC." + k), v.get("alias"))).append(", ");
        });
        sql.deleteCharAt(sql.length() - 2);
        sql.append(" FROM ").append(this.tableName).append(" EHC");
        return sqlService.selectData(sql.toString(), where, page, Collections.singletonList(sort));
    }

    private Where applicationCheck(Where param) {
//        boolean haveApplicationId = false;
        List<Where> params = new ArrayList<>();
        Where where = new Where(Where.Formula.AND, params);
        if (param != null) {
            params.add(param);
        }
//        不再做验证
//        for (PublicSearch p : param) {
//            if (APPLICATION_ID.equals(p.getField().toUpperCase()) || APPLICATION_ID.equals(CommonUtils.humpToLine(p.getField()).toUpperCase())) {
//                haveApplicationId = true;
//                break;
//            }
//        }
        // 判断是否添加 APPLICATION_ID 查询
        if (columns.contains(APPLICATION_ID)) {
            params.add(new Where(APPLICATION_ID, Where.Formula.IN, "0," + applicationId));
        }
        return where;
    }


//    /**
//     * 构建查询条件（更具实体类字段）
//     *
//     * @param criteria
//     * @param param
//     * @param actualClass
//     */
//    public void buildCriteria(Example.Criteria criteria, List<PublicSearch> param, Class<? extends Type> actualClass) {
//        List<String> thisColumns;
//        Map<String, String> thisFieldNames;
//        if (actualClass != null) {
//            thisColumns = new ArrayList<>();
//            thisFieldNames = new HashMap<>();
//            getColumnFields(actualClass, thisColumns, null, thisFieldNames, null);
//        } else {
//            thisColumns = this.columns;
//            thisFieldNames = this.fieldNames;
//        }
//        /**
//         * 属性名（驼峰命名）/数据库字段名
//         * 忽略大小写 数据库字段形式
//         * 属性.toUpperCase() == field.toUpperCase() || 属性.toUpperCase() == humpToLine(val.getField()).toUpperCase()
//         * 满足的话设置 field 为 对应的属性名
//         **/
//        for (Iterator<PublicSearch> it = param.iterator(); it.hasNext(); ) {
//            PublicSearch val = it.next();
//            if (thisColumns.contains(val.getField().toUpperCase()))
//                val.setField(thisFieldNames.get(val.getField().toUpperCase()));
//            else if (thisColumns.contains(CommonUtils.humpToLine(val.getField()).toUpperCase()))
//                val.setField(thisFieldNames.get(CommonUtils.humpToLine(val.getField()).toUpperCase()));
//            else it.remove();
//        }
//        for (PublicSearch search : param) {
//            switch (search.getFormulaE()) {
//                case GREATER:
//                    criteria.andGreaterThan(search.getField(), search.getCriteriaConditionVal());
//                    break;
//                case LESS:
//                    criteria.andLessThan(search.getField(), search.getCriteriaConditionVal());
//                    break;
//                case NOT_EQUAL:
//                    criteria.andNotEqualTo(search.getField(), search.getCriteriaConditionVal());
//                    break;
//                case LIKE:
//                    criteria.andLike(search.getField(), (String) search.getCriteriaConditionVal());
//                    break;
//                case END:
//                case START:
//                    criteria.andCondition(String.format("%s LIKE ", search.getField()), search.getCriteriaConditionVal());
//                    break;
//                case IN:
//                    List conditionVal = (List) search.getCriteriaConditionVal();
//                    if (conditionVal.size() > 0)
//                        criteria.andIn(search.getField(), conditionVal);
//                    else criteria.andCondition("1 = 2");
//                    break;
//                case NOT_IN:
//                    List conditionVal1 = (List) search.getCriteriaConditionVal();
//                    if (conditionVal1.size() > 0)
//                        criteria.andNotIn(search.getField(), conditionVal1);
//                    else criteria.andCondition("1 = 2");
//                    break;
//                case IS_NULL:
//                    criteria.andIsNull(search.getField());
//                    break;
//                case IS_NOT_NULL:
//                    criteria.andIsNotNull(search.getField());
//                    break;
//                case RANGE:
//                    PublicSearchRangeAssist range = (PublicSearchRangeAssist) search.getCriteriaConditionVal();
////                    criteria.andCondition(String.format("BETWEEN %s", search.getField()), range.getFront());
////                    criteria.andCondition(String.format("%s", search.getField()), range.getAfter());
//                    criteria.andBetween(search.getField(), range.getFront(), range.getAfter());
//                    break;
//                default:
//                    criteria.andEqualTo(search.getField(), search.getCriteriaConditionVal());
//            }
//        }
//    }

    public static void getColumnFields(Class<?> actualClass, List<String> columns, Map<String, Class<?>> idColumns, Map<String, String> fieldNames, Map<String, Map<String, String>> enhanceColumns) {
        if (actualClass == null || columns == null || fieldNames == null) {
            return;
        }
        Field[] fields = actualClass.getDeclaredFields();
        Arrays.stream(fields).forEach(field -> {
            if (field.isAnnotationPresent(Column.class)) {

                Column annotation = field.getAnnotation(Column.class);
                String name = annotation.name();
                if (field.isAnnotationPresent(Id.class)) {
                    idColumns.put(name.toUpperCase(), field.getType());
                }
                if (StringUtils.isNoneBlank(name)) {
                    fieldNames.put(name.toUpperCase(), field.getName());
                    columns.add(name.toUpperCase());
                }
                if (field.isAnnotationPresent(ModelColumnEnhance.class)) {
                    ModelColumnEnhance columnEnhance = field.getAnnotation(ModelColumnEnhance.class);
                    String sql = columnEnhance.sql();
                    String value = columnEnhance.value();
                    String alias = columnEnhance.alias();
                    if ((StringUtils.isNoneBlank(sql) || StringUtils.isNoneBlank(value)) && enhanceColumns != null) {
                        enhanceColumns.put(name.toUpperCase(), new HashMap<String, String>() {{
                            put("sql", StringUtils.isNoneBlank(sql) ? sql : value);
                            put("alias", StringUtils.isNoneBlank(alias) ? alias.toUpperCase() : String.format("%s_ENHANCE", name.toUpperCase()));
                        }});
                    }
                }
            }

        });
    }
}
